%%%-------------------------------------------------------------------
%%% @author liuwentao
%%% @doc
%%%     工具模板
%%% @end
%%% Created : 24. 5月 2021 14:27
%%%-------------------------------------------------------------------
-module(util).
-author("liuwentao").

%% API
-export([
    md5/1               %% md5加密
    , info/1            %% 打印
    , rand/2            %% 随机数
    , flist/1           %% 组装列表
    , flist/2           %% 组装列表
    , rand_list/1       %% 随机抽取
    , sleep/1           %% 进程挂起
    , get_patch/0       %% 获取根目录
    , load/1            %% 加载文件
    , save/2            %% 保存文件
    , term_to_string/1  %% 序列化
    , string_to_term/1  %% 反序列化
    , check_name/1      %% 检查名字
    , set_timer/3       %% 设置定时器
    , set_timer/4       %% 设置定时器
    , get_timer/1       %% 获取定时器
    , set_ms_timer/3    %% 设置毫秒定时器
    , set_ms_timer/4    %% 设置毫秒定时器
    , unset_all_timer/0 %% 关闭所有定时器
    , unset_timer/1     %% 关闭定时器
    , unset_timers/1    %% 清除多个定时器
    , round/1           %% 四舍五入
    , floor/1           %% 向下取整
    , ceil/1            %% 向上取整
    , time_left/2       %% 计算剩余时间，单位毫秒
]).

-include("common.hrl").

%% @doc 生成16位格式的md5值
-spec md5(iodata()) -> binary().
md5(Data) ->
    list_to_binary([io_lib:format("~2.16.0b", [N]) || N <- binary_to_list(erlang:md5(Data))]).


%% @doc 打印
info(Msg) ->
    ?ERR("~ts", [Msg]).


%% @doc 产生一个介于Min到Max之间的随机整数
-spec rand(Min :: integer(), Max :: integer()) -> integer().
rand(Min, Max) when Max < Min ->
    ?ERR("随机数错误的区间:[~w:~w:~w]", [Min, Max, get_stacktrace()]),
    rand(Max, Min);
rand(Min, Min) -> Min;
rand(Min, Max) ->
    case get(rand_seed) of
        undefined ->
            case crypto:strong_rand_bytes(12) of
                <<A:32, B:32, C:32>> ->
                    rand:seed(exsplus, {A, B, C}),
                    ok;
                _ ->
                    ignore
            end;
        _ ->
            ignore
    end,
    M = Min - 1,
    rand:uniform(Max - M) + M.


%% @doc 获取调用栈信息
get_stacktrace() ->
    try
        throw(a)
    catch
        _Type:_Err ->
            tl(erlang:get_stacktrace())
    end.


%% @doc 组装list
flist(Data) ->
    flist(Data, []).
flist(Data, Args) ->
    lists:flatten(io_lib:format(Data, Args)).


%% @doc 从一个list中随机取出一项
-spec rand_list(List :: list()) -> undefined | term().

rand_list([]) -> undefined;
rand_list([I]) -> I;
rand_list(List) ->
    Idx = rand(1, length(List)),
    get_term_from_list(List, Idx).
get_term_from_list([H | _T], 1) -> H;
get_term_from_list([_H | T], Idx) ->
    get_term_from_list(T, Idx - 1).


%% @doc 程序暂停执行时长(单位:毫秒)
-spec sleep(T :: integer()) -> ok.
sleep(T) ->
    receive
    after
        T -> ok
    end.


%% @doc 获取根目录
get_patch() ->
    env:get(code_path).


%% @doc 读取文件，并将内容转成term()
-spec load(string()) ->
    {ok, undefined} | {ok, term()} | {error, term()}.
load(File) ->
    case file:consult(File) of
        {error, Reason} -> {error, Reason};
        {ok, []} -> {ok, []};
        {ok, [Term]} -> {ok, Term}
    end.

%% @doc 将一个term()写入文件
-spec save(string(), term()) -> ok | {error, term()}.
save(File, Term) ->
    case file:open(File, [write]) of
        {error, Reason} -> {error, Reason};
        {ok, F} ->
            io:format(F, "~p.", [Term]),
            file:close(F),
            ok
    end.

%% @doc term序列化，term转换为string格式
-spec term_to_string(term()) -> string().
term_to_string(Term) -> io_lib:format("~w", [Term]).


%% @doc term反序列化，string转换为term
-spec string_to_term(String) -> {error, Reason} | {ok, term()} when
    String :: undefined | string() | bitstring(),
    Reason :: term().
string_to_term(undefined) -> {ok, undefined};
string_to_term("undefined") -> {ok, undefined};
string_to_term(String) when is_bitstring(String) ->
    string_to_term(binary_to_list(String));
string_to_term(String) ->
    S = re:replace(String, "<[0-9]+\\.[0-9]+\\.[0-9]+>", "undefined", [{return, list}, global]),
    case erl_scan:string(S ++ ".") of
        {ok, Tokens, _} -> erl_parse:parse_term(Tokens);
        {error, Err, _} -> {error, Err}
    end.

%% @doc 检查名称（会对长度、特殊字符屏蔽、敏感词屏蔽）
-spec check_name(bitstring()) -> true | {false, Reason::bitstring()}.
check_name(Text) ->
    %% 检查长度
    case keyword:check(name, unicode:characters_to_binary(Text)) of
        false -> {false, flist(?T("名字存在不和谐词汇，换个试试吧"))};
        true ->
            true
    end.




%% @doc 设置用进程字典存放的定时器
set_ms_timer(Name, MS, Info) -> set_ms_timer(Name, MS, self(), Info).
%% @doc 设置用进程字典存放的定时器
set_ms_timer(_Name, MS, _Pid, _Info) when is_integer(MS) andalso MS >= 4294967295 ->
    ignore;
set_ms_timer(Name, MS, Pid, Info) when is_integer(MS) ->
    case get('$dict_timer') of
        undefined ->
            put('$dict_timer',
                gb_trees:enter(Name, erlang:send_after(MS, Pid, Info), gb_trees:empty())
            );
        Tree ->
            case gb_trees:lookup(Name, Tree) of
                none ->
                    put('$dict_timer', gb_trees:enter(Name, erlang:send_after(MS, Pid, Info), Tree));
                {value, Ref} ->
                    erlang:cancel_timer(Ref),
                    put('$dict_timer',
                        gb_trees:enter(Name, erlang:send_after(MS, Pid, Info), gb_trees:delete(Name, Tree))
                    )
            end
    end.

%% @doc 设置用进程字典存放的定时器
set_timer(Name, TimeMix, Info) -> set_timer(Name, TimeMix, self(), Info).
set_timer(Name, {H, M, S}, Pid, Info) ->
    Sec = date:unixtime_diff({next, {H, M, S}}),
    set_timer(Name, Sec, Pid, Info);
set_timer(Name, Sec, Pid, Info) when is_integer(Sec) ->
    set_ms_timer(Name, Sec * 1000, Pid, Info).

%% @doc 获取用进程字典存放的定时器
get_timer(Name) ->
    case get('$dict_timer') of
        undefined -> false;
        Tree ->
            case gb_trees:lookup(Name, Tree) of
                none -> false;
                {value, Ref} ->
                    erlang:read_timer(Ref)
            end
    end.

%% @doc 清除用进程字典存放的Name定时器，清除set_timer/3,set_timer/4,set_ms_timer/3,set_ms_timer/4设置的定时器
unset_timer(Name) ->
    case get('$dict_timer') of
        undefined -> ok;
        Tree ->
            case gb_trees:lookup(Name, Tree) of
                none -> ok;
                {value, Ref} ->
                    erlang:cancel_timer(Ref),
                    put('$dict_timer', gb_trees:delete(Name, Tree))
            end
    end.

%% @doc 清除多个定时器
unset_timers(Names) ->
    case get('$dict_timer') of
        undefined -> ok;
        Tree ->
            unset_timers_f1(Names, Tree),
            ok
    end.

unset_timers_f1([], Tree) -> put('$dict_timer', Tree);
unset_timers_f1([Name | T], Tree) ->
    case gb_trees:lookup(Name, Tree) of
        none -> unset_timers_f1(T, Tree);
        {value, Ref} ->
            erlang:cancel_timer(Ref),
            unset_timers_f1(T, gb_trees:delete(Name, Tree))
    end.


%% @doc 清除用进程字典存放的所有定时器
unset_all_timer() ->
    case erase('$dict_timer') of
        undefined -> ok;
        Tree -> gb_trees:map(fun(_K, Ref) -> erlang:cancel_timer(Ref) end, Tree)
    end.


%% @doc  Float四舍五入
-spec round(number()) -> integer().
round(Float) ->
    erlang:round(Float).


%% @doc 取小于X的最大整数
-spec floor(number()) -> integer().
floor(X) ->
    T = erlang:trunc(X),
    case X < T of
        true -> T - 1;
        _ -> T
    end.

%% @doc 取大于X的最小整数
-spec ceil(number()) -> integer().
ceil(X) ->
    T = erlang:trunc(X),
    case X > T of
        true -> T + 1;
        _ -> T
    end.


%% @doc 计算剩余时间，单位：毫秒
-spec time_left(TimeMax::integer(), Begin::erlang:timestamp()) -> integer().
time_left(TimeMax, Begin)->
    T = util:floor(TimeMax - timer:now_diff(erlang:timestamp(), Begin) / 1000),
    case T > 0 of
        true -> T;
        false -> 0
    end.